/* 
 *  Incub - incubate your baby robot
 *  Copyright (C) 2008 Stanislaw Szymczyk
 *  e-mail: sszymczy@gmail.com   www: http://cogville.cog-lab.com
 *
 *  This file is part of Incub.
 *
 *  Incub is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  Incub is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with Incub.  If not, see <http://www.gnu.org/licenses/>.
 * 
 */
 
#ifndef XMLABLE_H_
#define XMLABLE_H_

#include <map>
#include <iostream>
#include <exception>
#include <typeinfo>
#include <vector>
#define TIXML_USE_TICPP
#include "ticpp.h"

#define REF_CLASS_DECLARE(clazz, ...) \
namespace xml { \
	class Xml ## clazz : public Xmlable { \
		private: Xml ## clazz() { \
			_type = new clazz ## Impl(); \
		} \
		protected: \
		static bool __init; \
		static bool init() { \
			std::string type = #clazz; \
			constructors[strdup(type.c_str())] = &create ## clazz; \
			__VA_ARGS__ \
			return true; \
		}	 \
		private: \
		static clazz* _type2; \
		public: \
		clazz* _type; \
		static Xmlable* create ## clazz(); \
		const char* type() { return #clazz; } \
		void* getType() { \
			return _type; \
		} \
		~Xml ## clazz() { \
		} \
	}; \
}

#define REF_CLASS_DEFINE(clazz) \
namespace xml { \
	Xmlable* Xml ## clazz::create ## clazz() { \
		std::cout << "Creating " #clazz << std::endl; \
		Xmlable* xml = new Xml ## clazz(); \
		return xml; \
	} \
	bool Xml ## clazz::__init = Xml ## clazz::init(); \
}

#define REF_SUPER(clazz) \
	supers[strdup(type.c_str())] = #clazz;

#define REF_ADDER_DECLARE(clazz) \
	return addAdderForType ## clazz(type); \
} \
static void add ## clazz(Xmlable* thiz, Xmlable* entity) { \
	reinterpret_cast<typeof _type2>(thiz->getType())->add ## clazz(reinterpret_cast<clazz*>(entity->getType())); \
} \
static bool addAdderForType ## clazz(std::string& type) { \
	adders[strdup(std::string(type+"_"+#clazz).c_str())] = (void(*)(Xmlable*, Xmlable*)) &add ## clazz;


#define REF_SETTER_DECLARE(attr) \
	return addSetterForAttr ## attr(type); \
} \
static void set ## attr(Xmlable* thiz, std::string& value) { \
	std::cout << "setting attribute " << value << " in " << thiz->getType() << std::endl; \
	reinterpret_cast<typeof _type2>(thiz->getType())->set ## attr(value); \
} \
static bool addSetterForAttr ## attr(std::string& type) { \
	std::string lowered = #attr; \
	lowered[0] = tolower(lowered[0]); \
	setters[strdup(std::string(type+"_"+lowered).c_str())] = (void(*)(Xmlable*, std::string)) &set ## attr;

#define REF_TYPED_SETTER(clazz) \
	return addTypedSetterForType ## clazz(type); \
} \
static void set ## clazz(Xmlable* thiz, Xmlable* entity) { \
	reinterpret_cast<typeof _type2>(thiz->getType())->set ## clazz(reinterpret_cast<clazz*>(entity->getType())); \
} \
static bool addTypedSetterForType ## clazz(std::string& type) { \
	typed_setters[strdup(std::string(type+"_"+#clazz).c_str())] = (void(*)(Xmlable*, Xmlable*)) &set ## clazz;

namespace xml {
	struct strCmp {
		bool operator()( const char* s1, const char* s2 ) const {
	//		std::cout << "comparing " << string(s1) << " and " << string(s2) << std::endl;
			return strcmp( s1, s2 ) < 0;
		}
	};

	class Xmlable {
		private:
		static std::vector<Xmlable*> entities;
		protected:
		static std::map<const char*, const char*, strCmp> supers;
		static std::map<const char*, Xmlable*(*)(void), strCmp> constructors;
		static std::map<const char*, void(*)(Xmlable*, Xmlable*), strCmp> adders;
		static std::map<const char*, void(*)(Xmlable*, Xmlable*), strCmp> typed_setters;
		static std::map<const char*, void(*)(Xmlable*, std::string), strCmp> setters;
		static bool __init;
		static bool init();
		public:
		class Exception : public std::exception {
			protected: std::string msg;
			public: 
			Exception(char *m) { msg = std::string(m); }
			virtual const char* what() const throw() { return msg.c_str(); }
			virtual ~Exception() throw() {};
		};
		class NoSuchClassException: public Exception
		{
			public: NoSuchClassException(char *msg) : Exception((char*)("I don't know how to instantiate class " + std::string(msg)).c_str()) { };
		};
		class NoSuchAdderException: public Exception
		{
			public: NoSuchAdderException(char *msg) : Exception((char*)("I don't know how to add object of class " + std::string(msg)).c_str()) { };
		};
		class NoSuchSetterException: public Exception
		{
			public: NoSuchSetterException(char *msg) : Exception((char*)("I don't know how to set attribute named " + std::string(msg)).c_str()) { };
		};
		static Xmlable* Create(char *name);
		void AddEntity(Xmlable*);
		void SetAttribute(std::string, std::string);
		void SetAttribute(Xmlable*);
		virtual const char* type() { return "Xmlable"; };
		void* _type;
		virtual void* getType() {
			return _type;
		}
		static void cleanup();
		virtual ~Xmlable() {};
	};
	
	xml::Xmlable* genesis(ticpp::Element* node);
}
#endif /*XMLABLE_H_*/
